########################################################################
#  Searx-qt - Lightweight desktop application for SearX.
#  Copyright (C) 2020  CYBERDEViL
#
#  This file is part of Searx-qt.
#
#  Searx-qt is free software: you can redistribute it and/or modify
#  it under the terms of the GNU General Public License as published by
#  the Free Software Foundation, either version 3 of the License, or
#  (at your option) any later version.
#
#  Searx-qt is distributed in the hope that it will be useful,
#  but WITHOUT ANY WARRANTY; without even the implied warranty of
#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#  GNU General Public License for more details.
#
#  You should have received a copy of the GNU General Public License
#  along with this program.  If not, see <https://www.gnu.org/licenses/>.
#
########################################################################


import re


class InstanceVersionTypes:
    No = 0  # just empty string, or malformed.
    Stable = 1
    Git = 2
    Unknown = 3  # string with the '-unknown' suffix


VERSION_PATTERNS = {
    InstanceVersionTypes.Stable:
        r"^([0-9]+)\.([0-9]+)\.([0-9]+)(-([0-9]+)\.([0-9]+)\.([0-9]+))?$",
    InstanceVersionTypes.Unknown:
        r"^([0-9]+)\.([0-9]+)\.([0-9]+)-unknown$",
    InstanceVersionTypes.Git:
        r"^([0-9]+)\.([0-9]+)\.([0-9]+)-([0-9]+)-([0-9A-Fa-f]{8})$"
}


class InstanceVersion:
    def __init__(
        self,
        str_,
        semVer=(0, 0, 0),
        verType=InstanceVersionTypes.No
    ):
        """
        @param semVer: Semantic version in a tuple
        @type semVer: tuple

        @param verType: Version type
        @type verType: int from InstanceVersionTypes
        """
        self.__str = str_
        self.__semVer = semVer
        self.__type = verType

    def __str__(self):
        return self.__str

    def data(self):
        return {
            'type': self.type,
            'semVer': self.semanticVersion
        }

    @property
    def semanticVersion(self):
        """ Returns the semantic version in a tuple (with 3 int's, Major, Minor
        and Patch)

        @rtype: tuple
        """
        return self.__semVer

    @property
    def type(self):
        """ Returns this version it's type; see class InstanceVersionTypes

        @rtype: str
        """
        return self.__type


class InstanceVersionGit(InstanceVersion):
    def __init__(self, str_, semVer, rev, shortHash):
        """
        @param semVer: Semantic version in a tuple
        @type semVer: tuple

        @param rev: Rev count
        @type rev: int

        @param shortHash: Short commit hash
        @type shortHash: str
        """
        InstanceVersion.__init__(
            self,
            str_,
            semVer=semVer,
            verType=InstanceVersionTypes.Git
        )
        self.__rev = rev
        self.__shortHash = shortHash

    def data(self):
        data = InstanceVersion.data(self)
        data.update({
            'rev': self.rev,
            'hash': self.shortHash
        })
        return data

    @property
    def rev(self):
        return self.__rev

    @property
    def shortHash(self):
        return self.__shortHash


def parseVersionString(versionStr):
    """
    @param versionStr: searx instance version string.
    @type: versionStr: str

    @rtype: InstanceVersion or InstanceVersionGit
    """
    if not versionStr:
        return InstanceVersion("")

    for versionType, pattern in VERSION_PATTERNS.items():
        result = re.match(pattern, versionStr)
        if result:
            semVer = (
                int(result.group(1)),
                int(result.group(2)),
                int(result.group(3))
            )
            if versionType == InstanceVersionTypes.Stable:
                return InstanceVersion(
                    versionStr,
                    semVer=semVer,
                    verType=InstanceVersionTypes.Stable
                )
            elif versionType == InstanceVersionTypes.Git:
                return InstanceVersionGit(
                    versionStr,
                    semVer,
                    int(result.group(4)),
                    result.group(5)
                )
            elif versionType == InstanceVersionTypes.Unknown:
                return InstanceVersion(
                    versionStr,
                    semVer=semVer,
                    verType=InstanceVersionTypes.Unknown
                )

    return InstanceVersion(versionStr)


def InstanceVersionGtEqCmp(versionA, versionB):
    """ Checks if versionA is greater or equal to versionB

    Used for minumum version filter.

    Notes:
     - Git versions (development) go above Stable version (with the same
       semantic version).

    @param versionA: InstanceVersion object to compare against versionB
    @type versionA: InstanceVersion

    @param versionB: InstanceVersion object to comapre against versionA
    @type versionB: InstanceVersion

    @rtype: bool
    """
    # Check if Major version is greater
    if versionA.semanticVersion[0] < versionB.semanticVersion[0]:
        return False
    elif versionA.semanticVersion[0] == versionB.semanticVersion[0]:
        # Check if Minor version is greater
        if versionA.semanticVersion[1] < versionB.semanticVersion[1]:
            return False
        elif versionA.semanticVersion[1] == versionB.semanticVersion[1]:
            # Check if Patch version is greater
            if versionA.semanticVersion[2] < versionB.semanticVersion[2]:
                return False

    # Check if semantic versions are equals
    if versionA.semanticVersion == versionB.semanticVersion:
        # Check if both are git versions
        if (
            versionA.type == InstanceVersionTypes.Git and
            versionB.type == InstanceVersionTypes.Git
        ):
            # Both are git versions, now compare the rev.
            if versionA.rev < versionB.rev:
                return False

        elif versionB.type == InstanceVersionTypes.Git:
            # Only versionB is type of Git, so it is greater then versionA
            return False

    return True
